package st.learnopengl.common

import gli_.gl
import gli_.gli
import org.lwjgl.glfw.GLFW.*
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL11.GL_TRUE
import org.lwjgl.system.MemoryStack.stackPush
import org.lwjgl.system.MemoryUtil.NULL
import java.lang.RuntimeException

abstract class App(var width: Int, var height: Int, val title: String) {
    fun run() {
        init()
        gli.gl.profile = gl.Profile.GL33
        onInit()

        while (!glfwWindowShouldClose(window)) {
            loop()

            glfwSwapBuffers(window)
            glfwPollEvents()
        }

        cleanUp()
    }

    private fun init() {
        GLFWErrorCallback.createPrint(System.err).set()
        if (!glfwInit()) {
            throw RuntimeException("GLFW init failed")
        }
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE)
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
        window = glfwCreateWindow(width, height, title, NULL, NULL)

        // Get the thread stack and push a new frame
        try {
            val stack = stackPush()
            val pWidth = stack.mallocInt(1)
            val pHeight = stack.mallocInt(1)

            glfwGetWindowSize(window, pWidth, pHeight)

            val fWidth = stack.mallocInt(1)
            val fHeight = stack.mallocInt(1)
            glfwGetFramebufferSize(window, fWidth, fHeight)

            val vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor())

            width = fWidth.get(0)
            height = fHeight.get(0)

            glfwSetWindowPos(
                window,
                (vidMode!!.width() - pWidth.get(0)) / 2,
                (vidMode!!.height() - pHeight.get(0)) / 2
            )
        } catch (all: Exception) {
            throw RuntimeException(all)
        }

        glfwMakeContextCurrent(window)
        glfwSwapInterval(1)
        glfwShowWindow(window)

        GL.createCapabilities()
    }

    /**
     * In this function create all OpenGL Object, shader, buffer, texture ...
     */
    abstract fun onInit()

    /**
     * custom render loop
     */
    abstract fun loop()

    /**
     * Free OpenGl resource
     */
    open fun cleanUp() {

    }

    private var window: Long = -1
}